using System;
using System.Linq;
using T.FAS.Runtime.Base.Entity;
using T.FAS.Runtime.Metadata.Entity.DataModel;
using T.FAS.Runtime.Metadata.Client;
using T.FAS.Runtime.Metadata.SPI;
using T.FAS.Runtime.StateMachine.Service;
using T.FAS.Runtime.DataAccess.SPI;
using T.FAS.Runtime.DataAccess.Service;
using System.Collections.Generic;
using T.ServiceComponent.Kernel.Service;
using T.FAS.Runtime.DataAccess.Common;

namespace T.FAS.Runtime.DataAccess.Service
{
    /// <summary>
    /// 状态机运行时客户端类
    /// </summary>
    public class StateMachineRTPClient
    {
        /// <summary>
        /// 调用来源，如果为AF则跳过权限校验
        /// </summary>
        private static readonly object s_lockObj = new object();
        private static StateMachineRTPClient s_instance = new StateMachineRTPClient();
        private const string InvokeSource = "Source";
        private readonly IMetaDataClientService _metaDataClientService = MetaDataClientService.GetInstance();
        private readonly IUnifiedDataAccessService _dataAccess = UnifiedDataAccessServiceFactory.GetInstance();

        private StateMachineRTPClient()
        {

        }

        public static StateMachineRTPClient GetInstance()
        {
            if(s_instance == null)
            {
                lock (s_lockObj)
                {
                    s_instance = new StateMachineRTPClient();
                }
            }
            return s_instance;
        }

        /// <summary>
        /// CDO给业务流提供的方法
        /// </summary>
        /// <param name="cdoId">CDO元数据id</param>
        /// <param name="colKey">绑定状态机的列名</param>
        /// <param name="eventKey">状态机事件Key</param>
        /// <param name="record">record数据</param>
        /// <returns></returns>
        public TResult ChangeState(string cdoId, string colKey, string eventKey, TRecord record)
        {

            string errmsg = null;
            if (string.IsNullOrEmpty(cdoId))
                errmsg += string.Format(CommonStringClassService.ArgumentCannotBeNull, nameof(cdoId));
            if (string.IsNullOrEmpty(colKey))
                errmsg += string.Format(CommonStringClassService.ArgumentCannotBeNull, nameof(colKey));
            if (string.IsNullOrEmpty(eventKey))
                errmsg += string.Format(CommonStringClassService.ArgumentCannotBeNull, nameof(eventKey));
            if (record == null)
                errmsg += string.Format(CommonStringClassService.ArgumentCannotBeNull, nameof(record));
            if (!string.IsNullOrEmpty(errmsg))
                throw new FasException(DataAccessErrorCode.StateMachine.NullValue, string.Format(CommonStringClassService.StateMachineRTPClient_ChangeState,errmsg));

            var cdoMeta = GetCompositeDataObject(cdoId);
            StateMachineMetadata stateMachine = GetStateMachineMetadata(cdoMeta, colKey);
            if (stateMachine == null)
                throw new FasException(DataAccessErrorCode.StateMachine.FireStateChangingError, $"CDO:{cdoId},colKey:{colKey}未获取到状态机元数据，请检查");
            try
            {
                var fireResult = StateMachineService.FireStateChanging(stateMachine, eventKey, record);
                var result = new TCommonResult<TRecord>();
                result.Record = fireResult;
                TResult tResult = TResultBuilder.BuildTResult(result, cdoMeta.BuildSchema());
                return tResult;
            }
            catch (Exception e)
            {

                throw new FasException(DataAccessErrorCode.StateMachine.FireStateChangingError, string.Format(CommonStringClassService.StateMachineRTPClient_FireEventFail), e);
            }
        }

        /// <summary>
        /// 初始化Record
        /// </summary>
        /// <param name="cdoId"></param>
        /// <param name="record"></param>
        /// <returns></returns>
        public TResult InitRecord(string cdoId, TRecord record)
        {
            if (string.IsNullOrEmpty(cdoId))
                throw new FasException(DataAccessErrorCode.StateMachine.NullValue, string.Format(CommonStringClassService.StateMachineRTPClient_InitRecord));
            if (record == null)
                throw new FasException(DataAccessErrorCode.StateMachine.NullValue, string.Format(CommonStringClassService.StateMachineRTPClient_InitRecord1));
            var cdoMeta = GetCompositeDataObject(cdoId);
            if (cdoMeta == null)
                throw new FasException(DataAccessErrorCode.Metadata.RTPGetMetadataError, string.Format(CommonStringClassService.CDOMetaData_GetInstanceFail,cdoId));
            try
            {
                var allStateMachines = GetCDOStateMachines(cdoMeta);
                
                foreach (var machine in allStateMachines)
                {
                    var firstEventKey = GetFirstEventKey(machine);
                    if (string.IsNullOrEmpty(firstEventKey))
                        throw new FasException(DataAccessErrorCode.StateMachine.TryGetInitKeyError, string.Format(CommonStringClassService.StateMachineRTPClient_InitRecord3,cdoId,cdoMeta.Code,machine.Code));
                    try
                    {
                        record = StateMachineService.FireStateChanging(machine, firstEventKey, record);
                    }
                    catch (Exception e)
                    {
                        throw new FasException(DataAccessErrorCode.StateMachine.FireStateChangingError, string.Format(CommonStringClassService.StateMachineRTPClient_InitRecord4,cdoId,cdoMeta.Code,machine.Code), e);
                    }
                }

                var result = new TCommonResult<TRecord>();
                result.Record = record;
                TResult tResult = TResultBuilder.BuildTResult(result, cdoMeta.BuildSchema());
                return tResult;
            }
            catch (Exception e)
            {
                throw new FasException(DataAccessErrorCode.StateMachine.FireStateChangingError, string.Format(CommonStringClassService.StateMachineRTPClient_InitRecord5,cdoId,cdoMeta.Code), e);
            }
        }

        /// <summary>
        /// CDO给审批流提供的方法，前置条件为BillID单据已存在，只需要执行Update方法
        /// </summary>
        /// <param name="cdoId">CDO元数据id</param>
        /// <param name="colKey">绑定状态机的列名</param>
        /// <param name="eventKey">状态机事件Key</param>
        /// <param name="billId">单据id</param>
        /// <param name="source">来源</param>
        /// <returns></returns>
        public TResult FireEvent(string cdoId, string colKey, string eventKey, string billId, string source = null)
        {

            string errmsg = null;
            if (string.IsNullOrEmpty(cdoId))
                errmsg += string.Format(CommonStringClassService.ArgumentCannotBeNull, nameof(cdoId));
            if (string.IsNullOrEmpty(colKey))
                errmsg += string.Format(CommonStringClassService.ArgumentCannotBeNull, nameof(colKey));
            if (string.IsNullOrEmpty(eventKey))
                errmsg += string.Format(CommonStringClassService.ArgumentCannotBeNull, nameof(eventKey));
            if (billId == null)
                errmsg += string.Format(CommonStringClassService.ArgumentCannotBeNull, nameof(billId));
            if (!string.IsNullOrEmpty(errmsg))
                throw new FasException(DataAccessErrorCode.StateMachine.NullValue, string.Format(CommonStringClassService.StateMachineRTPClient_FireEventFailWithMsg,errmsg));

            var cdoMeta = GetCompositeDataObject(cdoId);
            var billData = _dataAccess.QueryDataForCDO(cdoMeta, billId);
            var stateMachine = GetStateMachineMetadata(cdoMeta, colKey);
            TRecord fireResult;
            Dictionary<string, object> extParams = new Dictionary<string, object>();
            extParams[InvokeSource] = source;
            try
            {
                var record = ((TCommonResult<TRecord>)billData).Record;
                //审批流审批需要内置执行FireStateChanging事件动作，将状态切换到To状态
                fireResult = StateMachineService.FireStateChanging(stateMachine, eventKey, record, extParams);
            }
            catch (Exception e)
            {
                throw new FasException(DataAccessErrorCode.StateMachine.FireEventError, string.Format(CommonStringClassService.StateMachineRTPClient_FireEventFail), e);
            }

            try
            {
                //todo:优化状态机字段变更对接统一数据访问ChangeLog，需要手动调用变更日志服务构造ChangeSet。
                var recordWithChangeSet = ChangeSetService.Instance.GetFireEventChangeSet(cdoMeta, fireResult, colKey, eventKey);
                var finResult = _dataAccess.UpdateDataForCDO(cdoMeta, recordWithChangeSet, extParams);
                return finResult;
            }
            catch (Exception e)
            {
                throw new Exception(string.Format(CommonStringClassService.StateMachineRTPClient_CDOUpdateStateFail), e);
            }
        }

        /// <summary>
        /// CDO给审批流提供的方法，前置条件为BillID单据已存在，只需要执行Update方法
        /// </summary>
        /// <param name="cdoId">CDO元数据id</param>
        /// <param name="colKey">绑定状态机的列名</param>
        /// <param name="eventKey">状态机事件Key</param>
        /// <param name="record">单据id</param>
        /// <param name="source">来源</param>
        /// <returns></returns>
        public TResult FireEventWithTRecord(string cdoId, string colKey, string eventKey, TRecord record, string source = null)
        {
            string errmsg = null;
            if (string.IsNullOrEmpty(cdoId))
                errmsg += string.Format(CommonStringClassService.ArgumentCannotBeNull, nameof(cdoId));
            if (string.IsNullOrEmpty(colKey))
                errmsg += string.Format(CommonStringClassService.ArgumentCannotBeNull, nameof(colKey));
            if (string.IsNullOrEmpty(eventKey))
                errmsg += string.Format(CommonStringClassService.ArgumentCannotBeNull, nameof(eventKey));
            if (!string.IsNullOrEmpty(errmsg))
                throw new FasException(DataAccessErrorCode.StateMachine.NullValue, string.Format(CommonStringClassService.StateMachineRTPClient_FireEventFailWithMsg, errmsg));

            var cdoMeta = GetCompositeDataObject(cdoId);
            var stateMachine = GetStateMachineMetadata(cdoMeta, colKey);
            TRecord fireResult;
            Dictionary<string, object> extParams = new Dictionary<string, object>();
            extParams[InvokeSource] = source;
            try
            {
                //审批流审批需要内置执行FireStateChanging事件动作，将状态切换到To状态
                fireResult = StateMachineService.FireStateChanging(stateMachine, eventKey, record, extParams);
            }
            catch (Exception e)
            {
                throw new FasException(DataAccessErrorCode.StateMachine.FireEventError, string.Format(CommonStringClassService.StateMachineRTPClient_FireEventFail), e);
            }

            try
            {
                //todo:优化状态机字段变更对接统一数据访问ChangeLog，需要手动调用变更日志服务构造ChangeSet。
                var recordWithChangeSet = ChangeSetService.Instance.GetChangeSet(cdoMeta, Engine.ChangeSetType.Update, record);
                //var recordWithChangeSet = ChangeSetService.Instance.GetFireEventChangeSet(cdoMeta, fireResult, colKey, eventKey);
                var finResult = _dataAccess.UpdateDataForCDO(cdoMeta, recordWithChangeSet, extParams);
                return finResult;
            }
            catch (Exception e)
            {
                throw new Exception(string.Format(CommonStringClassService.StateMachineRTPClient_CDOUpdateStateFail), e);
            }
        }

        /// <summary>
        /// 运行时获取下个阶段可访问的状态集合，需要对接权限系统
        /// </summary>
        /// <param name="stateMachineID">状态机id</param>
        /// <param name="stateKey">状态key（int类型）</param>
        /// <returns></returns>
        public List<TState> GetTransitionStates(string stateMachineID, int stateKey, TRecord record = null)
        {
            string errmsg = null;
            if (string.IsNullOrEmpty(stateMachineID))
                errmsg += string.Format(CommonStringClassService.ArgumentCannotBeNull, nameof(stateMachineID));
            if (!string.IsNullOrEmpty(errmsg))
                throw new FasException(DataAccessErrorCode.StateMachine.NullValue, string.Format(CommonStringClassService.StateMachineRTPClient_FireEventFailWithMsg, errmsg));

            var StateMachine = GetStateMachineMetadataByID(stateMachineID);
            return StateMachineService.GetTransitionStates(StateMachine, stateKey, record);
        }

        #region Private Funcs
        /// <summary>
        /// 运行时获取CDO元数据
        /// </summary>
        /// <param name="cdoId"></param>
        /// <returns></returns>
        private CompositeDataObject GetCompositeDataObject(string cdoId)
        {
            try
            {
                var cdo = _metaDataClientService.GetMetaData(cdoId).Content as CompositeDataObject;
                return cdo;
            }
            catch (Exception e)
            {
                throw new FasException(DataAccessErrorCode.Metadata.RTPGetMetadataError, string.Format(CommonStringClassService.CDOMetaData_GetInstanceFail, cdoId), e);
            }
        }

        /// <summary>
        /// 通过CDO.id 列名获取状态机元数据
        /// </summary>
        /// <param name="CDOID"></param>
        /// <param name="colKey"></param>
        /// <returns></returns>
        private StateMachineMetadata GetStateMachineMetadata(CompositeDataObject cdo, string colKey)
        {
            if (string.IsNullOrEmpty(colKey))
                throw new FasException(DataAccessErrorCode.StateMachine.NullValue, string.Format(CommonStringClassService.ArgumentCannotBeNull,nameof(colKey)));
            if (cdo == null)
                return null;
            var stateMachine = cdo.RootCompositeNodeObject
                                    ?.Elements?.FirstOrDefault(e => colKey == (e.Alias ?? e.Name))
                                    ?.StateMachines?.FirstOrDefault();
            if (stateMachine == null || string.IsNullOrEmpty(stateMachine.ID))
                return null;
            try
            {
                var result = _metaDataClientService.GetMetaData(stateMachine.ID).Content as StateMachineMetadata;
                if (result == null)
                    throw new FasException(DataAccessErrorCode.Metadata.RTPGetMetadataError, string.Format(CommonStringClassService.StateMachineRTPClient_GetStateMachineMetadata1));
                return result;
            }
            catch (Exception e)
            {
                throw new FasException(DataAccessErrorCode.Metadata.RTPGetMetadataError, string.Format(CommonStringClassService.StateMachineRTPClient_GetStateMachineMetadata2,cdo.Code,stateMachine.ID,stateMachine.Code), e);
            }
        }

        /// <summary>
        /// 通过CDO.id 列名获取状态机元数据
        /// </summary>
        /// <param name="CDOID"></param>
        /// <param name="ColKey"></param>
        /// <returns></returns>
        private StateMachineMetadata GetStateMachineMetadataByID(string smId)
        {
            if (string.IsNullOrEmpty(smId))
                throw new FasException(DataAccessErrorCode.StateMachine.NullValue, string.Format(CommonStringClassService.ArgumentCannotBeNull, nameof(smId)));
            try
            {
                var result = _metaDataClientService.GetMetaData(smId).Content as StateMachineMetadata;
                return result;
            }
            catch (Exception e)
            {
                throw new FasException(DataAccessErrorCode.Metadata.RTPGetMetadataError, string.Format(CommonStringClassService.StateMachineRTPClient_GetStateMachineMetadataByID1,smId), e);
            }
        }

        /// <summary>
        /// 获取CDO的所有状态机集合
        /// </summary>
        /// <param name="cdo"></param>
        /// <returns></returns>
        private List<StateMachineMetadata> GetCDOStateMachines(CompositeDataObject cdo)
        {
            //拿到缺省的状态机数据集合
            //List<StateMachineMetadata> stateMachineMetadatas = cdo.RootCompositeNodeObject?
            //                                                      .Elements
            //                                                      .SelectMany(e => e.StateMachines)
            //                                                      ?.Where(sm => sm != null).ToList();
            List<StateMachineMetadata> stateMachineMetadatas = new List<StateMachineMetadata>();
            foreach (var item in cdo.RootCompositeNodeObject.Elements)
            {
                if(item.StateMachines != null && item.StateMachines.Count > 0)
                {
                    foreach (var s in item.StateMachines)
                    {
                        if (s != null)
                            stateMachineMetadatas.Add(s);
                    }
                }
            }
            List<StateMachineMetadata> result = new List<StateMachineMetadata>();
            foreach (var sm in stateMachineMetadatas)
            {
                var tmp = _metaDataClientService.GetMetaData(sm.ID).Content as StateMachineMetadata;
                if (tmp == null)
                    throw new FasException(DataAccessErrorCode.Metadata.RTPGetMetadataError, string.Format(CommonStringClassService.StateMachineRTPClient_GetCDOStateMachines,sm.ID));
                result.Add(tmp);
            }
            return result;
        }

        /// <summary>
        /// 获取状态机初始事件
        /// </summary>
        /// <param name="sm"></param>
        /// <returns></returns>
        private string GetFirstEventKey(StateMachineMetadata sm)
        {
            if (sm == null)
                return null;
            var eventid = sm.Transitions?.FirstOrDefault(t => string.IsNullOrEmpty(t.FromStateID))?.EventID;
            if (string.IsNullOrEmpty(eventid))
                throw new FasException(DataAccessErrorCode.StateMachine.MetadataError, string.Format(CommonStringClassService.StateMachineRTPClient_GetFirstEventKey,sm.Code));
            var eventKey = sm.Events?.FirstOrDefault(e => e.ID == eventid)?.Key;
            if (string.IsNullOrEmpty(eventKey))
                throw new FasException(DataAccessErrorCode.StateMachine.MetadataError, string.Format(CommonStringClassService.StateMachineRTPClient_GetFirstEventKey1,sm.Code,eventid));
            return eventKey;
        }
        #endregion
    }
}
